<template>
  <el-dialog
      :model-value="dialogVisible"
      :title="title"
      style="padding: 40px;margin-top: 10px"
      width="1800px"
      :close-on-click-modal="false"
      @close="handleClose"
  >
    <div class="terminal-container">
      <!-- 左侧主机列表 -->
      <div class="host-list">
        <el-menu
            :default-active="currentHostId"
            @select="handleHostSelect"
        >
          <el-menu-item
              v-for="host in hostList"
              :key="host[0]"
              :index="String(host[0])"
          >
            <span>{{ host[1] }}</span>
            <el-tag
                size="small"
                :type="getConnectionStatus(host[0])"
                style="margin-left: 10px"
            >
              {{ connectionStatus[host[0]] || '未连接' }}
            </el-tag>
          </el-menu-item>
        </el-menu>
      </div>

      <!-- 右侧终端区域 -->
      <div class="output-container">
        <template v-for="host in hostList" :key="host[0]">
          <div
              v-show="currentHostId === String(host[0])"
              :id="`terminal-${host[0]}`"
              class="terminal-viewer"
          ></div>
        </template>
      </div>
    </div>

    <template #footer>
      <el-button @click="handleClose" type="primary">关闭</el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import {ref, watch, nextTick, onBeforeUnmount} from "vue"
import {ElMessage} from "element-plus"
import {Terminal} from 'xterm'
import {FitAddon} from 'xterm-addon-fit'
import {WebLinksAddon} from "xterm-addon-web-links"
import 'xterm/css/xterm.css'
import requestUtil from "@/utils/request"
import CryptoJS from "crypto-js";
import Cookies from "js-cookie";

const props = defineProps({
  dialogTitle: {
    type: String,
    default: '',
    required: true
  },
  dialogVisible: {
    type: Boolean,
    default: false,
    required: true
  },
  textareaCmd: {
    type: String,
    default: '',
    required: true
  },
  receivedTuple: {
    type: Array,
    default: () => [],
    required: true
  }
})

const emits = defineEmits(['update:modelValue', 'initTableData'])

// 响应式数据
const cmd = ref('')
const hostList = ref([])
const title = ref('')
const currentHostId = ref('')
const connectionStatus = ref({})
const terminals = new Map()
const recvHostList = ref([])
// 终端会话管理
const sessions = ref([])

// 初始化终端
const initTerminal = (sessionID) => {
  const container = document.getElementById(`terminal-${sessionID}`)
  if (!container) {
    throw new Error(`找不到终端容器: terminal-${sessionID}`)
  }

  const terminal = new Terminal({
    cols: 160,
    rows: 38,
    convertEol: true, // 将换行符转换为回车换行
    disableStdin: false,     // 确保输入未被禁用
    screenReaderMode: true,  // 增强屏幕阅读器支持
    cursorBlink: true,
    fontSize: 14,
    scrollback: 1000,
    allowProposedApi: true, // 允许使用 proposed API
    scrollOnUserInput: true, // 用户输入时滚动到底部
    theme: {
      background: '#2e3436', // 背景色
      foreground: '#d3d7cf', // 前景色（文本颜色）
      cursor: '#e8e8e8',     // 光标颜色
      selection: '#d3d7cf',  // 选中文本的背景色
      black: '#171421',      // ANSI 黑色
      red: '#C01C28',        // ANSI 红色
      green: '#26A269',      // ANSI 绿色
      yellow: '#A2734C',     // ANSI 黄色
      blue: '#12488B',       // ANSI 蓝色
      magenta: '#A347BA',    // ANSI 洋红色
      cyan: '#2AA1B3',       // ANSI 青色
      white: '#D0CFCC',      // ANSI 白色
    },
  });
  const fitAddon = new FitAddon()
  terminal.loadAddon(fitAddon)
  terminal.loadAddon(new WebLinksAddon())

  terminal.open(container)
  fitAddon.fit()

  // 添加鼠标滚轮事件监听
  terminal.element.addEventListener('wheel', (event) => {
    if (event.deltaY < 0) {
      terminal.scrollLines(-3) // 向上滚动
    } else {
      terminal.scrollLines(3) // 向下滚动
    }
  })
  terminals.set(sessionID, {terminal, fitAddon})
}

// 处理主机切换
const handleHostSelect = (hostId) => {
  currentHostId.value = hostId
  nextTick(() => {
    const termInfo = terminals.get(hostId)
    termInfo?.fitAddon.fit()
  })
}

// 获取连接状态
const getConnectionStatus = (hostId) => {
  const status = connectionStatus.value[hostId]
  return status === 'connected' ? 'warning' : status === 'error' ? 'danger' :
      status === 'success' ? 'success' : 'info'
}

function calculateMD5(str) {
  return CryptoJS.MD5(str).toString(CryptoJS.enc.Hex);
}

// 执行批量命令
const sendHostListAndCmd = async () => {
  try {
    connectionStatus.value = {}

    await nextTick()

    hostList.value.forEach(host => {
          const hostId = host[0]

          const session = {
            id: hostId,
            connected: false,
            hostname: host[1],
            socket: null // 每个会话独立管理 WebSocket 实例
          }
          sessions.value.push(session)
          // 初始化终端
          initTerminal(session.id)

          const username = Cookies.get('username');
          const md5Value = calculateMD5(`job_${hostId}_${username}`);
          // 获取域名
          const domain = window.location.hostname;
          // 获取端口
          const port = window.location.port;
          // 建立WebSocket连接
          const wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${domain}:${port}${requestUtil.getServerUrl()}ws/${md5Value}`

          session.socket = new WebSocket(wsUrl)

          connectionStatus.value[hostId] = 'connecting'

          const term = terminals.get(session.id).terminal

          // WebSocket事件处理
          session.socket.onopen = () => {
            connectionStatus.value[hostId] = 'connected'
            term.writeln(`\x1B[32mShell脚本内容：\x1B[0m\r\n`)
            term.writeln(`$ ${cmd.value}\r\n`)
          }

          session.socket.onmessage = (e) => {
            try {
              const message = JSON.parse(e.data)
              switch (message.type) {
                case 'job_msg':
                  term.write(message.data)
                  break
                case 'job_error':
                  term.write(message.data)
                  session.socket.close()
                  connectionStatus.value[hostId] = 'error'
                  break
                case 'job_close':
                  term.write(message.data)
                  session.socket.close()
                  connectionStatus.value[hostId] = 'success'
                  break
              }
            } catch {
              term.write(e.data.replace(/\n/g, '\r\n'))
            }
          }

          session.socket.onerror = (error) => {
            connectionStatus.value[hostId] = 'error'
            term.writeln(`\x1B[31m连接错误：${error.message}\x1B[0m`)
          }

          session.socket.onclose = () => {
            console.log(`已断开ws连接：${md5Value}`)
          }
        }
    )

    if (hostList.value.length > 0) {
      currentHostId.value = String(hostList.value[0][0])
    }
    const res = await requestUtil.post('host/job', {
      host_list: hostList.value.map(h => h[0]),
      cmd: cmd.value
    })
    const data = res.data
    // const errorStr = ref('')
    if (data.code !== 200) {
      // errorStr.value = data.msg
      // data.errors.forEach(err => {
      //   errorStr.value += `<br>${err}`
      // })
      // ElMessage.error({
      //   message: errorStr.value,
      //   dangerouslyUseHTMLString: true // 允许HTML标签
      // })
      recvHostList.value = data.err_host_id
    }
  } catch
      (error) {
    ElMessage.error(`请求失败：${error.message}`)
  }
}

// 关闭处理
const handleClose = () => {
  recvHostList.value = []
  terminals.forEach((termInfo) => {
    termInfo.terminal.dispose()
  })
  terminals.clear()
  sessions.value.forEach(session => {
    if (session.socket?.readyState === WebSocket.OPEN) {
      session.socket.close()
    }
  })
  emits('update:modelValue', false)
  emits('initTableData')
}

// 窗口大小处理
const handleResize = () => {
  terminals.forEach(({fitAddon}) => {
    fitAddon.fit()
  })
}

// 监听可见性变化
watch(() => props.dialogVisible,
    (visible) => {
      if (visible) {
        cmd.value = props.textareaCmd
        title.value = props.dialogTitle
        hostList.value = props.receivedTuple
        nextTick(() => {
              sendHostListAndCmd()
              window.addEventListener('resize', handleResize)
            }
        )
      } else {
        window.removeEventListener('resize', handleResize)
      }
    }
)
// 监听有报错返回的主机id
watch(() => recvHostList.value.length,
    () => {
      recvHostList.value.forEach(hostId => {
        connectionStatus.value[hostId] = 'error';
      })
    }
)

onBeforeUnmount(() => {
  handleClose()
  window.removeEventListener('resize', handleResize)
})
</script>

<style scoped>
.terminal-container {
  display: flex;
  height: 650px;
  border: 1px solid #ebeef5;
}

.host-list {
  width: 300px;
  border-right: 1px solid #ebeef5;
  overflow-y: auto;
}

.output-container {
  flex: 1;
  background: #2e3436;
  position: relative;
  overflow: hidden; /* 隐藏滚动条 */
}

.terminal-viewer {
  height: 650px;
  width: 100%;
  padding: 10px 5px 10px 10px;
  box-sizing: border-box;
}

:deep(.xterm) {
  padding: 10px;
  height: 97%;
}

:deep(.xterm-viewport) {
  overflow-y: auto !important; /* 启用垂直滚动条 */
  scrollbar-width: thin; /* Firefox */
}

/* Chrome/Safari 滚动条样式 */
:deep(.xterm-viewport::-webkit-scrollbar) {
  width: 8px;
}

:deep(.xterm-viewport::-webkit-scrollbar-track) {
  background: #2e3436;
}

:deep(.xterm-viewport::-webkit-scrollbar-thumb) {
  background: #555;
  border-radius: 4px;
}

:deep(.xterm-viewport::-webkit-scrollbar-thumb:hover) {
  background: #777;
}

:deep(.el-menu-item) {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 45px;
  line-height: 45px;
  padding: 0 16px;
}
</style>